package common;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

public final class DateTimeHelper
{
    /**
     * Date format used in URI or URI parameters. Example: 20100429.
     */
    public static final String URI_DATEONLY = "yyyyMMdd";
    /**
     * Date time format used in URI or URI parameters. This is to be specified in UTC (Z). Example:
     * 20100429T235123Z. We use this compact format to avoid URL encoding. HH instead of hh for
     * 24-hour clock.
     */
    public static final String URI_DATETIME = "yyyyMMdd'T'HHmmss'Z'";
    /**
     * Date format used in payload. Example: 2010-04-29.
     */
    public static final String PAYLOAD_DATEONLY = "yyyy-MM-dd";
    /**
     * Date time format used in payload. Example: 2010-04-29T23:51:23+0000. HH instead of hh for
     * 24-hour clock.
     */
    public static final String PAYLOAD_DATETIME = "yyyy-MM-dd'T'HH:mm:ssZ";
    /**
     * Date time format used in SQL DB.
     */
    public static final String SQL_DATETIME = "yyyy-MM-dd HH:mm:ss";

    /**
     * Number of milliseconds per second.
     */
    private static final long MILLISECOND_PER_SECOND = 1000L;
    private static final double MILLISECOND_PER_SECOND_FLOAT = 1000.0;

    private static final float DAY_TO_SEC_COOEF = 60 * 60 * 24;

    private DateTimeHelper()
    {
    }

    private static DatatypeFactory datatypeFactory;

    private static DatatypeFactory getDatatypeFactory()
    {
        if (datatypeFactory == null)
        {
            // cache a copy of this, it was showing up high on the profile list
            synchronized (DateTimeHelper.class)
            {
                if (datatypeFactory == null)
                {
                    try
                    {
                        datatypeFactory = DatatypeFactory.newInstance();
                    }
                    catch (Exception e)
                    {
                        throw new RuntimeException("date format incorrect");
                    }
                }
            }
        }
        return datatypeFactory;
    }

    /**
     * Convert to an instance of the xml gregorian calendar (for xml serialization).
     * 
     * @param date
     * @return
     */
    public static XMLGregorianCalendar convertToXMLGregorianCalendar(Date date)
    {
        GregorianCalendar gregCal = new GregorianCalendar();
        gregCal.setTime(date);
        DatatypeFactory dFactory = getDatatypeFactory();
        return dFactory.newXMLGregorianCalendar(gregCal);
    }

    /**
     * Convert from the xml gregorian calendar type to a java.util.Date value.
     * 
     * @param calendar
     * @return
     */
    public static Date convertFromXMLGregorianCalendar(XMLGregorianCalendar calendar)
    {
        // When we convert, we 'normalize' the value to UTC. Normally,
        // with the getTime method, this would be handled, however, due to
        // a bug in the apache libs, the cnversion fails. This, we normalize
        // it first.
        return calendar.normalize().toGregorianCalendar().getTime();
    }

    /**
     * Given the format value parse the date time from the string.
     * 
     * @param value
     * @param format
     * @return
     * @throws ParseException
     */
    public static Date parseExact(String value, String format) throws ParseException
    {
        DateFormat formatter = new SimpleDateFormat(format);
        return formatter.parse(value);
    }

    /**
     * Get a java date time given an Epoch timestamp in second (the DB format).
     * 
     * @param timestamp
     */
    public static Date getDateFromEpochTimeInSecond(long timestamp)
    {
        return new Date(timestamp * MILLISECOND_PER_SECOND);
    }

    /**
     * Get a an Epoch timestamp in second (the DB format) from java date time.
     * 
     * @param timestamp
     */
    public static int getEpochTimeInSecond(Date date)
    {
        return (int) (date.getTime() / MILLISECOND_PER_SECOND);
    }

    /**
     * Get a an Epoch timestamp in second (the DB format) from system currentTimeMillis.
     * 
     * @param timestamp
     */
    public static int getEpochTimeInSecond(long date)
    {
        return (int) (date / MILLISECOND_PER_SECOND);
    }

    /**
     * Get a date (no time).
     * 
     * @param year
     * @param month
     * @param day
     * @return
     */
    public static Date getDate(int year, int month, int day)
    {
        Calendar cal = Calendar.getInstance();
        cal.clear();
        cal.set(year, month, day);
        return cal.getTime();
    }

    /**
     * Get the date
     * 
     * @param year
     * @param month
     * @param day
     * @param hour
     * @param minute
     * @param second
     * @return
     */
    public static Date getDate(int year, int month, int day, int hour, int minute, int second)
    {
        Calendar cal = Calendar.getInstance();
        cal.clear();
        cal.set(year, month, day, hour, minute, second);
        return cal.getTime();
    }

    /**
     * Get the start of the given calendar day.
     * 
     * @param day
     * @return
     */
    public static Date getStartOfDay(Date day)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(day);
        cal.set(Calendar.AM_PM, Calendar.AM);
        cal.set(Calendar.HOUR, cal.getActualMinimum(Calendar.HOUR));
        cal.set(Calendar.MINUTE, cal.getActualMinimum(Calendar.MINUTE));
        cal.set(Calendar.SECOND, cal.getActualMinimum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getActualMinimum(Calendar.MILLISECOND));

        return cal.getTime();
    }

    /**
     * Get the end of the given calendar day.
     * 
     * @param day
     * @return
     */
    public static Date getEndOfDay(Date day)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(day);
        cal.set(Calendar.AM_PM, Calendar.PM);
        cal.set(Calendar.HOUR, cal.getActualMaximum(Calendar.HOUR));
        cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
        cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));

        return cal.getTime();
    }

    /**
     * Get the start of week for the given calendar day.
     * 
     * @param day the day.
     * @return the start of week for the day.
     */
    public static Date getStartOfWeek(final Date day)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(day);
        cal.set(Calendar.DAY_OF_WEEK, cal.getActualMinimum(Calendar.DAY_OF_WEEK));
        cal.set(Calendar.AM_PM, Calendar.AM);
        cal.set(Calendar.HOUR, cal.getActualMinimum(Calendar.HOUR));
        cal.set(Calendar.MINUTE, cal.getActualMinimum(Calendar.MINUTE));
        cal.set(Calendar.SECOND, cal.getActualMinimum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getActualMinimum(Calendar.MILLISECOND));

        return cal.getTime();
    }

    /**
     * Get the end of week for the given calendar day.
     * 
     * @param day the day.
     * @return the end of week for the given day.
     */
    public static Date getEndOfWeek(Date day)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(day);
        cal.set(Calendar.DAY_OF_WEEK, cal.getActualMaximum(Calendar.DAY_OF_WEEK));
        cal.set(Calendar.AM_PM, Calendar.PM);
        cal.set(Calendar.HOUR, cal.getActualMaximum(Calendar.HOUR));
        cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
        cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));

        return cal.getTime();
    }

    /**
     * Get the start of month for the given calendar day.
     * 
     * @param day the day.
     * @return the start of month for the given day.
     */
    public static Date getStartOfMonth(Date day)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(day);
        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH));
        cal.set(Calendar.AM_PM, Calendar.AM);
        cal.set(Calendar.HOUR, cal.getActualMinimum(Calendar.HOUR));
        cal.set(Calendar.MINUTE, cal.getActualMinimum(Calendar.MINUTE));
        cal.set(Calendar.SECOND, cal.getActualMinimum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getActualMinimum(Calendar.MILLISECOND));

        return cal.getTime();
    }

    /**
     * Get the end of month for the given calendar day.
     * 
     * @param day the day.
     * @return the end of month for the given day.
     */
    public static Date getEndOfMonth(Date day)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(day);
        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
        cal.set(Calendar.AM_PM, Calendar.PM);
        cal.set(Calendar.HOUR, cal.getActualMaximum(Calendar.HOUR));
        cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
        cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));

        return cal.getTime();
    }

    /**
     * Get next day (add Day)of the given calendar day.
     * 
     * @param day
     * @return
     */
    public static Date getNextDay(Date day)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(day);
        cal.add(Calendar.DATE, 1);
        return cal.getTime();
    }

    public static Date addDays(Date originalDay, int numDays)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(originalDay);
        cal.add(Calendar.DAY_OF_MONTH, numDays);
        Date newDay = new Date(cal.getTimeInMillis());

        return newDay;
    }

    public static Date addHours(Date originalDay, int numHours)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(originalDay);
        cal.add(Calendar.HOUR_OF_DAY, numHours);
        Date newDay = new Date(cal.getTimeInMillis());

        return newDay;
    }

    public static Date getMax(Date day1, Date day2)
    {
        if (day1 == null)
        {
            return day2;
        }
        if (day2 == null)
        {
            return day1;
        }
        Calendar cal1 = Calendar.getInstance();
        Calendar cal2 = Calendar.getInstance();

        cal1.setTime(day1);
        cal2.setTime(day2);

        if (cal1.before(cal2))
        {
            return cal2.getTime();
        }
        else
        {
            return cal1.getTime();
        }
    }

    /**
     * Takes a date and a date format string and returns a string of the date formatted according to
     * the passed in arguments
     * 
     * @param date - date to turn into a string
     * @param dateFormatString - string specifying how the date is to be formatted into a string
     * @return String - the date argument in a string representation according to the passed in
     *         dateFormatString
     */
    public static String getStringFromDate(Date date, String dateFormatString)
    {
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormatString);
        return sdf.format(date);
    }

    /**
     * Takes a date in string format and a date format string and returns a Date
     * 
     * @param dateString - the date in a string representation
     * @param dateFormatString - the format of how the date is represented in the dateString
     *            argument
     * @return Date - returns a date object as specified by the dateString argument
     */
    public static Date getDateFromString(String dateString, String dateFormatString)
    {
        try
        {
            Date result = parseExact(dateString, dateFormatString);
            return result;
        }
        catch (ParseException e)
        {
            throw new RuntimeException("Date could not be parsed in the specified format: "
                            + dateFormatString + " - " + e.getMessage());
        }
    }

    /**
     * Returns weather the date is within a given range.
     * 
     * @return
     */
    public static boolean isDateInRange(Date dateToCheck, Date startDate, Date endDate)
    {
        return !((startDate != null && dateToCheck.before(startDate)) || (endDate != null && dateToCheck.after(endDate)));
    }

    /**
     * Returns weather the date is within a given range. - Governed by one field
     * 
     * @return
     */
    public static boolean isDateInRange(Date dateToCheck, Date midRange, Integer startRange,
                    Integer endRange, int fieldType)
    {

        Date startDate = null;
        if (startRange != null)
        {
            Calendar startCal = Calendar.getInstance();
            startCal.setTime(midRange);
            startCal.add(fieldType, startRange);
            startDate = startCal.getTime();
        }

        Date endDate = null;
        if (endRange != null)
        {
            Calendar endCal = Calendar.getInstance();
            endCal.setTime(midRange);
            endCal.add(fieldType, endRange);
            endDate = endCal.getTime();
        }

        return isDateInRange(dateToCheck, startDate, endDate);
    }

    public static long getDiffInSeconds(Date firstDay, Date secondDay)
    {
        long diffInMsec = Math.abs(firstDay.getTime() - secondDay.getTime());
        long diffInSec = Math.round(diffInMsec / MILLISECOND_PER_SECOND_FLOAT); // just round the
                                                                                // double to seconds

        return diffInSec;
    }

    /**
     * @param seconds
     * @return
     */
    public static double convertSecondsToDays(long seconds)
    {
        return seconds / DAY_TO_SEC_COOEF;
    }

    /**
     * @param days
     * @return
     */
    public static double convertDaysToSeconds(long days)
    {
        return days * DAY_TO_SEC_COOEF;
    }
}
